package com.lianqi.emcpframework.logging.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import com.lianqi.emcpframework.logging.context.InvokingLoggerContext;
import com.lianqi.emcpframework.logging.event.InvokingLoggerListener;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.Ordered;
import org.springframework.util.ClassUtils;

import com.lianqi.emcpframework.core.boost.aop.MethodInterceptorHolder;
import com.lianqi.emcpframework.core.generator.RequestIDGenerator;
import com.lianqi.emcpframework.logging.context.LoggerDefinition;
import com.lianqi.emcpframework.logging.event.InvokingLoggerAfterEvent;
import com.lianqi.emcpframework.logging.event.InvokingLoggerBeforeEvent;
import com.lianqi.emcpframework.utils.WebUtil;

/**
 * @program emcp-framework
 * @ClassName AopInvokingLoggerSupport
 * @description: aop方式访问日志生成
 * @author: sky
 * @create: 2018/11/17 16:52
 */
public class AopInvokingLoggerSupport extends StaticMethodMatcherPointcutAdvisor {

    @Autowired(required = false)
    private final List<InvokingLoggerListener> listeners = new ArrayList<>();
    @Autowired(required = false)
    private final List<InvokingLoggerParser> loggerParsers = new ArrayList<>();
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Override
    public boolean matches(Method method, Class<?> aClass) {
        return loggerParsers.stream().allMatch(parser -> parser.support(aClass, method));
    }

    /**
     * 添加自定义logger监听
     * 
     * @param listener
     *            方法调用日志监听器
     * @return this
     */
    public AopInvokingLoggerSupport addListener(InvokingLoggerListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
        return this;
    }

    /**
     * 添加自定义logger转换器
     * 
     * @param parser
     *            方法调用日志转换器
     * @return this
     */
    public AopInvokingLoggerSupport addParser(InvokingLoggerParser parser) {
        if (!loggerParsers.contains(parser)) {
            loggerParsers.add(parser);
        }
        return this;
    }

    /**
     * 构造方法 初始化
     * 
     * @see MethodInterceptorHolder
     * @see InvokingLoggerContext
     */
    public AopInvokingLoggerSupport() {
        setAdvice((MethodInterceptor)methodInvocation -> {
            MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
            InvokingLoggerContext context = createLoggerContext(holder);
            Object response;
            try {
                eventPublisher.publishEvent(new InvokingLoggerBeforeEvent(context));
                listeners.forEach(listener -> {
                    listener.onLogBefore(context);
                });
                response = methodInvocation.proceed();
                context.setResponse(response);
            } catch (Throwable e) {
                context.setException(e);
                throw e;
            } finally {
                context.setResponseTime(System.currentTimeMillis());
                eventPublisher.publishEvent(new InvokingLoggerAfterEvent(context));
                listeners.forEach(listener -> listener.onlogging(context));
            }
            return response;
        });
    }

    /**
     * 创建方法访问日志上下文
     * 
     * @param holder
     *            方法拦截器holder
     * @return 日志上下文
     */
    private InvokingLoggerContext createLoggerContext(MethodInterceptorHolder holder) {
        InvokingLoggerContext context = new InvokingLoggerContext();

        context.setId(RequestIDGenerator.getInstance().nextId());
        context.setRequestTime(System.currentTimeMillis());
        LoggerDefinition definition = loggerParsers.stream()
            .filter(parser -> parser.support(ClassUtils.getUserClass(holder.getTarget()), holder.getMethod())).findAny()
            .map(parser -> parser.parse(holder)).orElse(null);
        if (null != definition) {
            context.setAction(definition.getAction());
            context.setDescribe(definition.getDescribe());
            context.setActionTypes(definition.getActionTypes());
        }
        context.setParameters(holder.getArgs());
        context.setTarget(holder.getTarget().getClass());
        context.setMethod(holder.getMethod());
        HttpServletRequest request = WebUtil.getHttpServletRequest();
        if (null != request) {
            context.setRequestHeaders(WebUtil.getHeaderMapper(request));
            context.setIp(WebUtil.getIpAddr(request));
            context.setHttpMethod(request.getMethod());
            context.setUrl(request.getRequestURL().toString());
        }
        return context;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
